1.Обзор системы | 2. Мой первый проект | 3. Модель | 4. Представление | 5. Табличное представление | 6. Объектно-реляционное связывание | 7. Контроллеры | 8. Приложение | 9. Расширенные возможности

Глава 5: Табличное представление данных(Tabular data)

OpenXava имеет встроенные возможности профессионального качества для работы с табличным представлением данных. Создав обычный OX модуль, не написав ни строчки дополнительного программного кода, вы сразу даете возможность пользователю работать с комплексными данными в виде таблиц:
tab_en010.jpg
Возможности управления данными в таблице для стандартной таблицы OX:
  • Фильтрация по любому столбцу или комбинации столбцов.
  • Сортировка по любом столбцу, назначение сортировки по заданному столбцу одним щелчком мыши.
  • Постраничное представление больших массивов данных (paging). Пользователь может эффективно работать с миллионами записей.
  • Легкое управление столбцами таблицы: добавление, удаление и изменение порядка столбцов (вызывается щелчком по изображению маленького карандаша в левом углу). Даннные изменения запоминаются для каждого пользователя индивидуально.
  • Базовые действия для управления набором записей в таблице: генерация отчетов в виде PDF, экспорт в Excel, а также удаление выбранных объектов.
Стандартная реализация таблицы в OX покрывает большинство типовых случаев использования, более того, пользователь может донастроить её согласно своим предпочтениям. Тем не менее, в некоторых случаях бывает нужно изменить поведение таблицы. Для этого в OX реализована специальная аннотация @Tab, которую можно использовать совместно с определением POJO класса (бизнес-компонента).
Сигнатура @Tab приведена ниже:
 @Tab(
 name="name", // 1
 filter=filter class, // 2
 rowStyles=array of @RowStyle, // 3
 properties="properties", // 4
 baseCondition="base condition", // 5
 defaultOrder="default order" // 6
 )
 public class MyEntity {
 
  1. name (необязательно): Для одного класса (entity) вы можете определить несколько табличных представлений (используя аннотацию @Tabs), при этом для каждой нужно задать собственное имя. Имя используется для указания, какое именно табличное представление использовать, если таких представлений задано несколько (обычно указывается в application.xml).
  2. filter (необязательно): Позволяет определить некоторую программную логику, применяемую к данным, которые вводит пользователь, когда он определяет фильтр для отображаемых в табличном представлении данных.
  3. rowStyles (необязательно): Позволяет наипростейшим путем задать разные стили отображения для некоторых строк. Обычно используется для того, чтобы выделить строки, удовлетворяющие определенному условию. Стили вы задаете через массив стилей строк @RowStyle. Таким путем вы можете использовать несколько стилей в одной таблице.
  4. properties (необязательно): Список свойств для первоначального отображения. В списке могут быть использованы полные имена (которые задаются как referenceName.propertyName, при этом вложенность может быть любой степени).
  5. baseCondition (необязательно): Условие, которому должны удовлетворять выводимые данные. Оно добавляется к пользовательскому фильтру, если он задан.
  6. defaultOrder (необязательно): Определяет первоначальный порядок сортировки записей в таблице.

Первоначальные свойства и выделенные строки (Initial properties and emphasize rows)

Наиболее простая модификация табличного представления состоит в том, чтобы задать список и порядок свойств, отображаемых первоначально:
@Tab(
  rowStyles=@RowStyle(style="row-highlight", property="type", value="steady"),
  properties="name, type, seller.name, address.city, seller.level.description, address.state.name"
)
 
Указанные свойства первоначально отображаются при выполнении модуля. Далее пользователь имеет возможность настроить список и порядок свойств для отображения по своему усмотрению. Напоминаю, что сложных свойств таких как ссылочные необходимо указывать полные имена (referenceName.propertyName), при этом вложенность имен может быть любой.

Кроме того, в выше указанном примере вы можете увидеть, как с помощью @RowStyle переопределить некоторых строк таблицы. В нашем случае, строка представяющая объект, имеющий свойство type со значением steady, будет иметь стиль row-highlight (подсвечено или выделено). Указанный стиль должен быть определен в таблице каскадных стилей CSS приложения. Стиль how-highlight (highlight in versions previous to v4m3) стандартный для OpenXava и уже определен, но ничто не мешает вам добавлять собственные стили.
Визуальное представление вышесказанного будет:
tab_en020.jpg

Фильтры и базовое условие (Filters and base condition)

Общераспространенной практикой является техника комбинирования фильтра с базовым условием:
 @Tab(name="Current",
 filter=CurrentYearFilter.class,
 properties="year, number, amountsSum, vat, detailsCount, paid, customer.name",
 baseCondition="${year} = ?"
 )
 
Обратите внимание, что базовое условие имеет синтаксис SQL. Вы можете использовать ? для подстановки значений и ${} для указания имен свойств. В данном примере фильтр используется для того, чтобы задать значение. Ниже приведен программный код фильтра:
 package org.openxava.test.filters;
 
 import java.util.*;
 
 import org.openxava.filters.*;
 
 /**
 * @author Javier Paniza
 */
 
 public class CurrentYearFilter implements IFilter { // (1)
 
 public Object filter(Object o) throws FilterException { // (2)
 Calendar cal = Calendar.getInstance();
 cal.setTime(new java.util.Date());
 Integer year = new Integer(cal.get(Calendar.YEAR));
 Object [] r = null;
 if (o == null) { // (3)
 r = new Object[1];
 r[0] = year;
 }
 else if (o instanceof Object []) { // (4)
 Object [] a = (Object []) o;
 r = new Object[a.length + 1];
 r[0] = year;
 for (int i = 0; i < a.length; i++) {
 r[i+1]=a[i];
 }
 }
 else { // (5)
 r = new Object[2];
 r[0] = year;
 r[1] = o;
 }
 
 return r;
 }
 
 }
Данный фильтр принимает на вход данные, введённые пользователем как агрументы фильтрации в веб форме под заголовком таблицы. Фильтр возвращает значение, которое передается в OpenXava для выполнения запроса. Как вы видите в листинге, класс фильтра должен реализовывать интерфейс IFilter (1). Что принуждает класс реализовать метод с именем filter (2). Данный метод получает объект со значениеми аргументов фильтрации и возвращает фильтрующее значение, которое будет использовано как аргумент запроса.
Аргументы фильтрации, передаваемые в метод, могут быть:
  • null (3), если пользователь не ввовел никаких значений в соответствующие поля формы
  • один простой объект (5), если пользователь ввел одно значение или
  • массив объектов (4), если пользователь ввел несколько значений.
Данный фильтр учитвать все условия.Фильтр в примере добавляет текущий год как первый аргумент и данное значение используется для заполнения агрументов в базовом условии baseCondition таблицы.
В результате таблица, которую вы видите ниде, отображает только счета, относящиеся к текущему году.
Рассмотрим другой пример определения таблицы:
 @Tab(name="DefaultYear",
 filter=DefaultYearFilter.class,
 properties="year, number, customer.number, customer.name, amountsSum, " +
 "vat, detailsCount, paid, importance",
 baseCondition="${year} = ?"
 )
 
Для вышеуказанного примера программный код фильтра будет таким:
 package org.openxava.test.filters;
 
 import java.util.*;
 
 import org.openxava.filters.*;
 
 /**
 * @author Javier Paniza
 */
 
 public class DefaultYearFilter extends BaseContextFilter { // (1)
 
 public Object filter(Object o) throws FilterException {
 if (o == null) {
 return new Object [] { getDefaultYear() }; // (2)
 }
 if (o instanceof Object []) {
 List c = new ArrayList(Arrays.asList((Object []) o));
 c.add(0, getDefaultYear()); // (2)
 return c.toArray();
 }
 else {
 return new Object [] { getDefaultYear(), o }; // (2)
 }
 }
 
 private Integer getDefaultYear() throws FilterException {
 try {
 return getInteger("xavatest_defaultYear"); // (3)
 }
 catch (Exception ex) {
 ex.printStackTrace();
 throw new FilterException(
 "Impossible to obtain default year associated with the session");
 }
 }
 
 }
Данный фильтр расширяет BaseContextFilter, что позволяет обращаться к сессионным объектам (session objects) OpenXava без дополнительных усилий. В примере выше, вы можете увидеть, как в коде фильтра идет обращение к методу getDefaultYear() (2). Который в свою очередь обращается к значению сессионного объекта xavatest_defaultYear. Не забывайте приводить ссылку на сессионный объект к нужному типу. В примере это сделано вызовом getInteger() (3). Также можно использовать любые другие методы, в засимости от типа сессионного объекта, например, getString(), getLong(). Также вы можете использовать нетипизированный get()).
Обсуждаемый сессионный объект xavatest_defaultYear определен в controllers.xml следующим образом:
 <object name="xavatest_defaultYear" class="java.lang.Integer" value="1999"/>
Действия (actions) могут модифицировать такие объекты. Жизненный цикл таких объектов совпадает с жизненным циклом пользовательской сессии. Но каждый модуль OX имеет свою копию сессионных переменных. Более подробную информацию вы найдете в Главе 7.
Такой подход удачен для отображения в режиме списка (таблицы) данных в зависимости от пользователя или конфигурации, выбранной пользователем.
Также, возможно обращаться к переменным среды внутри фильров типа BaseContextFilter, используя метод getEnvironment(), см. пример :
 new Integer(getEnvironment().getValue("XAVATEST_DEFAULT_YEAR"));
Дополнительная информация о переменных окружения доступна в Главе 7 Контроллеры.

Явные SQL-запросы

OpenXava позволяет вам описывать базовое условие, используя синтаксис SQL. Вы можете написать полное SQL выражения SELECTдля выборки данных в табличное представление:
 @Tab(name="CompleteSelect",
 properties="number, description, family",
 baseCondition=
 "select" +
 " ${number}, ${description}, XAVATEST.FAMILY.DESCRIPTION " +
 "from " +
 " XAVATEST.SUBFAMILY, XAVATEST.FAMILY " +
 "where " +
 " XAVATEST.SUBFAMILY.FAMILY = " +
 " XAVATEST.FAMILY.NUMBER"
 )
 
Используете данную технику только в исключительных случаях. Т.к. в случае её применения пользователь не сможет настраивать внешний вид табличного представления.

Первоначальный порядок сортировки записей (default order)

И наконец, установка порядка сортировки записей по-умолчанию выполняется очень просто:
 @Tab(name="Simple", properties="year, number, date",
 defaultOrder="${year} desc, ${number} desc"
 )
 
Выражение defaultOrder задает первичный порядок сортировки перечислением полей, по которым сортируются данные, в порядке участия в сортировке. Кроме этого, для каждого поля задается направление сортировки, в нашем примере, это desc - в порядке убывания записей.
Далее пользователь свободен в определении порядка сортировки, для задания порядка сортировки по конкретному столбцу достаточно щелкнуть мышкой по ссылке в названии столбца.